﻿using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using WP7Contrib.View.Controls.Extensions;

namespace MetroInMotionUtils
{
  /// <summary>
  /// Attached properties that expose the first item and scroll direction 
  /// of an ItemsControl
  /// </summary>
  public class ListUtils
  {
    #region IsScrollingUpwards attached property

    /// <summary>
    /// Gets whether the ItemsControl is scrolling up.
    /// </summary>
    public static bool GetIsScrollingUpwards(DependencyObject depObj)
    {
      return (bool)depObj.GetValue(IsScrollingUpwardsProperty);
    }

    /// <summary>
    /// Sets whether the ItemsControl is scrolling up.
    /// </summary>
    private static void SetIsScrollingUpwards(DependencyObject depObj, bool value)
    {
      depObj.SetValue(IsScrollingUpwardsProperty, value);
    }

    public static readonly DependencyProperty IsScrollingUpwardsProperty =
        DependencyProperty.RegisterAttached("IsScrollingUpwards", typeof(bool),
        typeof(ListUtils), new PropertyMetadata(false));

    #endregion

    #region FirstVisibleItem attached property

    /// <summary>
    /// Gets the first item visible in the ItemsControl
    /// </summary>
    public static object GetFirstVisibleItem(DependencyObject depObj)
    {
      return (object)depObj.GetValue(FirstVisibleItemProperty);
    }

    /// <summary>
    /// Sets the first item visible in the ItemsControl
    /// </summary>
    private static void SetFirstVisibleItem(DependencyObject depObj, object value)
    {
      depObj.SetValue(FirstVisibleItemProperty, value);
    }

    public static readonly DependencyProperty FirstVisibleItemProperty =
        DependencyProperty.RegisterAttached("FirstVisibleItem", typeof(object),
        typeof(ListUtils), new PropertyMetadata(null));

    #endregion

    #region ExposeFirstVisibleItem attached property

    /// <summary>
    /// Gets whether to expose a FirstVisibleItem attached property
    /// </summary>
    public static bool GetExposeFirstVisibleItem(DependencyObject depObj)
    {
      return (bool)depObj.GetValue(ExposeFirstVisibleItemProperty);
    }

    /// <summary>
    /// Sets whether to expose a FirstVisibleItem attached property
    /// </summary>
    public static void SetExposeFirstVisibleItem(DependencyObject depObj, object value)
    {
      depObj.SetValue(ExposeFirstVisibleItemProperty, value);
    }

    public static readonly DependencyProperty ExposeFirstVisibleItemProperty =
        DependencyProperty.RegisterAttached("ExposeFirstVisibleItem", typeof(bool),
        typeof(ListUtils), new PropertyMetadata(OnExposeFirstVisibleItemPropertyChanged));

    #endregion

    /// <summary>
    /// Handles changes to the ExposeFirstVisibleItem property, wiring up the scrollbar
    /// in order to update the FirstVisibleItem property accordingly
    /// </summary>
    private static void OnExposeFirstVisibleItemPropertyChanged(DependencyObject d,
        DependencyPropertyChangedEventArgs e)
    {
      if (e.NewValue.Equals(true))
      {
        ItemsControl itemsControl = d as ItemsControl;

        // wire up the scrollbar, handling ValueChanged events
        if (!WireUpScrollbar(itemsControl))
        {
          // if wire-up fails, try again on LayoutUpdated
          EventHandler tryFindScrollBar = null;
          tryFindScrollBar = (s2, e2) =>
          {
            if (WireUpScrollbar(itemsControl))
            {
              itemsControl.LayoutUpdated -= tryFindScrollBar;
            }
          };
          itemsControl.LayoutUpdated += tryFindScrollBar;
        }
      }
    }

    /// <summary>
    /// Locates the vertical scrollbar for the given itemsControl
    /// and wires event handlers to update the FirstVisibleItem attached
    /// property on scroll.
    /// </summary>
    private static bool WireUpScrollbar(ItemsControl itemsControl)
    {
      var sb = itemsControl.Descendants<ScrollBar>()
                          .Cast<ScrollBar>()
                          .Where(s => s.Orientation == System.Windows.Controls.Orientation.Vertical)
                          .SingleOrDefault() as ScrollBar;

      if (sb == null)
        return false;
    
      // set to the initial value
      SetFirstVisibleItem(itemsControl);
      sb.Tag = sb.Value;

      // update value on scroll ...
      sb.ValueChanged += (s, e2) =>
      {
        SetFirstVisibleItem(itemsControl);
        SetIsScrollingUpwards(itemsControl, sb.Value < (double)sb.Tag);

        // store the previous scroll position in the Tag
        sb.Tag = sb.Value;
      };

      return true;
    }

    /// <summary>
    /// Sets the FirstVisibleItem for the ItemsControl
    /// </summary>
    private static void SetFirstVisibleItem(ItemsControl itemsControl)
    {
      itemsControl.Dispatcher.BeginInvoke(() =>
        {
          var item = itemsControl.GetItemsInView().First();
          ListUtils.SetFirstVisibleItem(itemsControl, item.DataContext);
        });
    }

  }

}

